﻿using PmSoft.Core;
using StackExchange.Redis;

namespace PmSoft.Cache.Redis;

public static class RedisCacheProviderExtensions
{
	// 获取带实例名前缀的键
	private static string GetPrefixedKey(IRedisCacheProvider cache, string key) => $"{cache.Options.InstanceName}{key}";

	#region 基本键值操作

	/// <summary>异步检查键是否存在</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">键</param>
	public static async Task<bool> KeyExistsAsync(this IRedisCacheProvider cache, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		return await cache.Database.KeyExistsAsync(GetPrefixedKey(cache, key));
	}

	/// <summary>异步设置字符串值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">键</param>
	/// <param name="value">值</param>
	/// <param name="expiry">过期时间，可选</param>
	/// <param name="nx">仅当键不存在时设置，可选</param>
	public static async Task<bool> SetAsync<T>(this IRedisCacheProvider cache, string key, T value, TimeSpan? expiry = null, bool nx = false, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		ArgumentNullException.ThrowIfNull(value);
		return await cache.Database.StringSetAsync(GetPrefixedKey(cache, key), Json.Stringify(value), expiry, nx ? When.NotExists : When.Always);
	}

	/// <summary>异步删除键</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">键</param>
	public static async Task<bool> KeyDeleteAsync(this IRedisCacheProvider cache, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		return await cache.Database.KeyDeleteAsync(GetPrefixedKey(cache, key));
	}

	/// <summary>异步获取字符串值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">键</param>
	public static async Task<T?> GetAsync<T>(this IRedisCacheProvider cache, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		var val = await cache.Database.StringGetAsync(GetPrefixedKey(cache, key));
		return val.IsNullOrEmpty ? default : Json.Parse<T?>(val.ToString());
	}
	#endregion 基本键值操作

	#region 列表操作

	/// <summary>异步左侧推入列表</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">列表的键</param>
	/// <param name="value">要推入的值</param>
	/// <param name="nx">仅当键不存在时推入</param>
	public static async Task<long> ListLeftPushAsync<T>(this IRedisCacheProvider cache, string key, T value, bool nx = false, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		ArgumentNullException.ThrowIfNull(value);
		return await cache.Database.ListLeftPushAsync(GetPrefixedKey(cache, key), Json.Stringify(value), nx ? When.NotExists : When.Always);
	}

	/// <summary>异步右侧推入列表</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">列表的键</param>
	/// <param name="value">要推入的值</param>
	/// <param name="nx">仅当键不存在时推入</param>
	public static async Task<long> ListRightPushAsync<T>(this IRedisCacheProvider cache, string key, T value, bool nx = false, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		ArgumentNullException.ThrowIfNull(value);
		return await cache.Database.ListRightPushAsync(GetPrefixedKey(cache, key), Json.Stringify(value), nx ? When.NotExists : When.Always);
	}

	/// <summary>异步从左侧弹出元素</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">列表的键</param>
	public static async Task<T?> ListLeftPopAsync<T>(this IRedisCacheProvider cache, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		var val = await cache.Database.ListLeftPopAsync(GetPrefixedKey(cache, key));
		return val.IsNullOrEmpty ? default : Json.Parse<T?>(val.ToString());
	}

	/// <summary>异步从右侧弹出元素</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">列表的键</param>
	public static async Task<T?> ListRightPopAsync<T>(this IRedisCacheProvider cache, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		var val = await cache.Database.ListRightPopAsync(GetPrefixedKey(cache, key));
		return val.IsNullOrEmpty ? default : Json.Parse<T?>(val.ToString());
	}

	/// <summary>异步获取列表长度</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">列表的键</param>
	public static async Task<long> ListLengthAsync(this IRedisCacheProvider cache, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		return await cache.Database.ListLengthAsync(GetPrefixedKey(cache, key));
	}

	
	#endregion 列表操作

	#region 哈希操作
	/// <summary>同步删除哈希字段</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashFields">要删除的字段集合</param>
	public static long HashDelete(this IRedisCacheProvider cache, string hashKey, IEnumerable<string> hashFields)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashFields);
		return cache.Database.HashDelete(GetPrefixedKey(cache, hashKey), hashFields.Select(x => (RedisValue)x).ToArray());
	}
	/// <summary>异步删除哈希字段</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashFields">要删除的字段集合</param>
	public static async Task<long> HashDeleteAsync(this IRedisCacheProvider cache, string hashKey, IEnumerable<string> hashFields, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashFields);
		return await cache.Database.HashDeleteAsync(GetPrefixedKey(cache, hashKey), hashFields.Select(x => (RedisValue)x).ToArray());
	}
	/// <summary>同步删除单个哈希字段</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">要删除的字段</param>
	public static bool HashDelete(this IRedisCacheProvider cache, string hashKey, string hashField)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return cache.Database.HashDelete(GetPrefixedKey(cache, hashKey), hashField);
	}
	/// <summary>异步删除单个哈希字段</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">要删除的字段</param>
	public static async Task<bool> HashDeleteAsync(this IRedisCacheProvider cache, string hashKey, string hashField, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return await cache.Database.HashDeleteAsync(GetPrefixedKey(cache, hashKey), hashField);
	}
	/// <summary>同步设置哈希字段值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">字段名</param>
	/// <param name="value">字段值</param>
	/// <param name="nx">仅当字段不存在时设置</param>
	public static bool HashSet<T>(this IRedisCacheProvider cache, string hashKey, string hashField, T value, bool nx = false)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return cache.Database.HashSet(GetPrefixedKey(cache, hashKey), hashField, Json.Stringify(value), nx ? When.NotExists : When.Always);
	}
	/// <summary>异步设置哈希字段值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="key">字段名</param>
	/// <param name="value">字段值</param>
	/// <param name="nx">仅当字段不存在时设置</param>
	public static async Task<bool> HashSetAsync<T>(this IRedisCacheProvider cache, string hashKey, string key, T value, bool nx = false, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(key);
		return await cache.Database.HashSetAsync(GetPrefixedKey(cache, hashKey), key, Json.Stringify(value), nx ? When.NotExists : When.Always);
	}
	/// <summary>同步获取哈希字段值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="key">字段名</param>
	public static T? HashGet<T>(this IRedisCacheProvider cache, string hashKey, string key)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(key);
		var val = cache.Database.HashGet(GetPrefixedKey(cache, hashKey), key);
		return val.IsNullOrEmpty ? default : Json.Parse<T?>(val.ToString());
	}
	/// <summary>异步获取哈希字段值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="key">字段名</param>
	public static async Task<T?> HashGetAsync<T>(this IRedisCacheProvider cache, string hashKey, string key, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(key);
		var val = await cache.Database.HashGetAsync(GetPrefixedKey(cache, hashKey), key);
		return val.IsNullOrEmpty ? default : Json.Parse<T?>(val.ToString());
	}
	/// <summary>同步获取多个哈希字段值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="keys">字段名集合</param>
	public static Dictionary<string, T> HashGet<T>(this IRedisCacheProvider cache, string hashKey, IEnumerable<string> keys)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(keys);
		var vals = cache.Database.HashGet(GetPrefixedKey(cache, hashKey), keys.Select(m => (RedisValue)m).ToArray());
		var dict = new Dictionary<string, T>();
		for (int i = 0; i < keys.Count(); i++)
		{
			if (vals[i].HasValue && !dict.ContainsKey(keys.ElementAt(i)))
			{
				var val = Json.Parse<T?>(vals[i].ToString());
				if (val != null) dict.Add(keys.ElementAt(i), val);
			}
		}
		return dict;
	}
	/// <summary>异步获取多个哈希字段值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="keys">字段名集合</param>
	public static async Task<Dictionary<string, T>> HashGetAsync<T>(this IRedisCacheProvider cache, string hashKey, IEnumerable<string> keys, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(keys);
		var vals = await cache.Database.HashGetAsync(GetPrefixedKey(cache, hashKey), keys.Select(m => (RedisValue)m).ToArray());
		var dict = new Dictionary<string, T>();
		for (int i = 0; i < keys.Count(); i++)
		{
			if (vals[i].HasValue && !dict.ContainsKey(keys.ElementAt(i)))
			{
				var val = Json.Parse<T?>(vals[i].ToString());
				if (val != null) dict.Add(keys.ElementAt(i), val);
			}
		}
		return dict;
	}
	/// <summary>同步获取哈希所有值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	public static IEnumerable<T?> HashValues<T>(this IRedisCacheProvider cache, string hashKey)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		return cache.Database.HashValues(GetPrefixedKey(cache, hashKey)).Select(x => x.HasValue ? Json.Parse<T?>(x.ToString()) : default);
	}
	/// <summary>异步获取哈希所有值</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	public static async Task<IEnumerable<T?>> HashValuesAsync<T>(this IRedisCacheProvider cache, string hashKey, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		var hashValues = await cache.Database.HashValuesAsync(GetPrefixedKey(cache, hashKey));
		return hashValues.Select(x => x.HasValue ? Json.Parse<T?>(x.ToString()) : default);
	}
	/// <summary>同步获取哈希所有键值对</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	public static Dictionary<string, string> HashGetAll(this IRedisCacheProvider cache, string hashKey)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		return cache.Database.HashGetAll(GetPrefixedKey(cache, hashKey)).ToStringDictionary();
	}
	/// <summary>异步获取哈希所有键值对</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	public static async Task<Dictionary<string, string>> HashGetAllAsync(this IRedisCacheProvider cache, string hashKey)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		return (await cache.Database.HashGetAllAsync(GetPrefixedKey(cache, hashKey))).ToStringDictionary();
	}
	/// <summary>同步获取哈希长度</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	public static long HashLength(this IRedisCacheProvider cache, string hashKey)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		return cache.Database.HashLength(GetPrefixedKey(cache, hashKey));
	}
	/// <summary>异步获取哈希长度</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	public static async Task<long> HashLengthAsync(this IRedisCacheProvider cache, string hashKey, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		return await cache.Database.HashLengthAsync(GetPrefixedKey(cache, hashKey));
	}
	/// <summary>同步检查哈希字段是否存在</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">字段名</param>
	public static bool HashExists(this IRedisCacheProvider cache, string hashKey, string hashField)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return cache.Database.HashExists(GetPrefixedKey(cache, hashKey), hashField);
	}
	/// <summary>异步检查哈希字段是否存在</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">字段名</param>
	public static async Task<bool> HashExistsAsync(this IRedisCacheProvider cache, string hashKey, string hashField, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return await cache.Database.HashExistsAsync(GetPrefixedKey(cache, hashKey), hashField);
	}
	/// <summary>同步哈希字段值递增</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">字段名</param>
	/// <param name="value">增量，默认1</param>
	public static long HashIncrement(this IRedisCacheProvider cache, string hashKey, string hashField, long value = 1)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return cache.Database.HashIncrement(GetPrefixedKey(cache, hashKey), hashField, value);
	}
	/// <summary>异步哈希字段值递增</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">字段名</param>
	/// <param name="value">增量，默认1</param>
	public static async Task<long> HashIncrementAsync(this IRedisCacheProvider cache, string hashKey, string hashField, long value = 1, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return await cache.Database.HashIncrementAsync(GetPrefixedKey(cache, hashKey), hashField, value);
	}
	/// <summary>异步哈希字段值递减</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="hashKey">哈希的键</param>
	/// <param name="hashField">字段名</param>
	/// <param name="value">减量，默认1</param>
	public static async Task<long> HashDecrementAsync(this IRedisCacheProvider cache, string hashKey, string hashField, long value = 1, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(hashKey);
		ArgumentNullException.ThrowIfNull(hashField);
		return await cache.Database.HashDecrementAsync(GetPrefixedKey(cache, hashKey), hashField, value);
	}
	/// <summary>同步设置键的过期时间</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">键</param>
	/// <param name="expiry">过期时间</param>
	public static bool SetKeyExpire(this IRedisCacheProvider cache, string key, TimeSpan expiry)
	{
		ArgumentNullException.ThrowIfNull(key);
		return cache.Database.KeyExpire(GetPrefixedKey(cache, key), expiry);
	}
	#endregion 哈希操作

	#region 锁操作
	/// <summary>同步获取锁</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	/// <param name="value">锁的值，用于标识锁拥有者</param>
	/// <param name="expiry">锁的过期时间</param>
	public static bool LockTake(this IRedisCacheProvider cache, string key, string value, TimeSpan expiry, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		return cache.Database.LockTake(GetPrefixedKey(cache, key), value, expiry);
	}
	/// <summary>同步释放锁</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	/// <param name="value">锁的值，用于验证锁拥有者</param>
	public static bool LockRelease(this IRedisCacheProvider cache, string key, string value, CancellationToken token = default)
	{
		ArgumentNullException.ThrowIfNull(key);
		return cache.Database.LockRelease(GetPrefixedKey(cache, key), value);
	}
	/// <summary>查询锁状态</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	public static string LockQuery(this IRedisCacheProvider cache, string key)
	{
		ArgumentNullException.ThrowIfNull(key);
		return cache.Database.LockQuery(GetPrefixedKey(cache, key)).ToString();
	}
	/// <summary>异步释放锁</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	/// <param name="value">锁的值，用于验证锁拥有者</param>
	public static async Task<bool> LockReleaseAsync(this IRedisCacheProvider cache, string key, string value, CancellationToken token)
	{
		ArgumentNullException.ThrowIfNull(key);
		return await cache.Database.LockReleaseAsync(GetPrefixedKey(cache, key), value).ConfigureAwait(false);
	}
	/// <summary>异步获取锁</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	/// <param name="value">锁的值，用于标识锁拥有者</param>
	/// <param name="expiry">锁的过期时间</param>
	public static async Task<bool> LockTakeAsync(this IRedisCacheProvider cache, string key, string value, TimeSpan expiry, CancellationToken token)
	{
		ArgumentNullException.ThrowIfNull(key);
		return await cache.Database.LockTakeAsync(GetPrefixedKey(cache, key), value, expiry).ConfigureAwait(false);
	}
	/// <summary>异步等待并获取锁</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	/// <param name="value">锁的值，用于标识锁拥有者</param>
	/// <param name="maxWait">最大等待时间</param>
	/// <param name="lockExpiry">锁的过期时间</param>
	public static async Task<bool> WaitUseLockAsync(this IRedisCacheProvider cache, string key, string value, TimeSpan maxWait, TimeSpan lockExpiry, CancellationToken token)
	{
		var totalTime = TimeSpan.Zero;
		var sleepTime = TimeSpan.FromMilliseconds(100);
		while (totalTime < maxWait)
		{
			if (await cache.LockTakeAsync(key, value, lockExpiry, token).ConfigureAwait(false))
				return true;
			await Task.Delay(sleepTime, token);
			totalTime += sleepTime;
		}
		return false;
	}
	/// <summary>异步使用锁执行操作</summary>
	/// <param name="cache">Redis缓存提供者</param>
	/// <param name="key">锁的键</param>
	/// <param name="value">锁的值，用于标识锁拥有者</param>
	/// <param name="maxWait">最大等待时间</param>
	/// <param name="lockExpiry">锁的过期时间</param>
	/// <param name="success">获取锁成功时执行的函数</param>
	/// <param name="fail">获取锁失败时执行的函数，可选</param>
	public static async Task<T?> UseLockAsync<T>(this IRedisCacheProvider cache, string key, string value, TimeSpan maxWait, TimeSpan lockExpiry, Func<T> success, Func<T> fail = null, CancellationToken token = default)
	{
		if (await cache.WaitUseLockAsync(key, value, maxWait, lockExpiry, token).ConfigureAwait(false))
		{
			try
			{
				return success();
			}
			finally
			{
				await cache.LockReleaseAsync(key, value, token).ConfigureAwait(false);
			}
		}
		return fail != null ? fail() : default;
	}
	#endregion 锁操作
}